Obsidian EndTime 1.1.5
前のバージョン: Obsidian EndTime 1.1.4
次のバージョン: Obsidian EndTime 1.1.6
常に今日のデイリーノート(yyyy-MM-dd.md)を読み込むように変更
code:manifest.json
{
"id": "endtime-obsidian",
"name": "End Time",
"version": "1.1.5",
"minAppVersion": "0.12.0",
"author": "Shino",
"description": "Displays task information with estimated end time in the sidebar.",
"isDesktopOnly": false
}
code:main.js
const { ItemView, WorkspaceLeaf } = require("obsidian");
const VIEW_TYPE_TASK_SIDEBAR = "task-sidebar-view";
class TaskSidebarView extends ItemView {
constructor(leaf) {
super(leaf);
}
getViewType() {
return VIEW_TYPE_TASK_SIDEBAR;
}
getDisplayText() {
return "Task Sidebar";
}
async onOpen() {
console.log("TaskSidebarView opened.");
const container = this.containerEl.children1; // Safely access the second child of containerEl
if (!container) {
console.error("Container not found in TaskSidebarView.");
return;
}
container.empty();
container.createEl("h4", { text: "Task Schedule" });
}
async updateTasks(taskList) {
// console.log("Updating task list in TaskSidebarView:", taskList);
const container = this.containerEl.children1;
if (!container) {
console.error("Container not found when updating tasks.");
return;
}
container.empty(); // Clear previous task list
taskList.forEach((task) => {
let taskName, timeInfo = task.split('||'); // Split taskName and timeInfo by '||'
// Remove the URL part in Markdown links (e.g., Link Text(http://example.com) => "Link Text")
taskName = taskName.replace(/\[(^\]+)\]\(^\)+\)/g, '$1');
const taskItem = container.createEl("div", { cls: "task-item" });
// Create first line for task name or section
let taskNameEl;
if (taskName.startsWith("🗂️")) {
taskNameEl = taskItem.createEl("p", { text: taskName, cls: "section" }); // Apply 'section' class
} else {
taskNameEl = taskItem.createEl("p", { text: taskName, cls: "task-name" }); // Apply 'task-name' class
}
// Create second line for time info (grayed out and smaller font)
const timeEl = taskItem.createEl("p", { text: timeInfo, cls: "task-time" });
timeEl.style.color = "#888"; // Gray out the text
timeEl.style.fontSize = "0.9em"; // Slightly smaller font size
});
}
async onClose() {
console.log("TaskSidebarView closed.");
}
}
module.exports = class TaskSchedulePlugin extends require("obsidian").Plugin {
async onload() {
console.log("Task Schedule Plugin loaded.");
// Register the sidebar view
this.registerView(VIEW_TYPE_TASK_SIDEBAR, (leaf) => new TaskSidebarView(leaf));
// Add a command to display the task info in the sidebar
this.addCommand({
id: "show-tasks-sidebar",
name: "Show Tasks in Sidebar",
callback: () => this.showTasksSidebar(),
});
// Open the view when the plugin is loaded
this.activateView();
// Update tasks every 1 second (1000 milliseconds)
this.updateInterval = setInterval(() => {
this.showTasksSidebar();
}, 1000); // 1000 milliseconds = 1 second
}
onunload() {
console.log("Task Schedule Plugin unloaded.");
this.app.workspace.detachLeavesOfType(VIEW_TYPE_TASK_SIDEBAR);
// Clear the interval when the plugin is unloaded
clearInterval(this.updateInterval);
}
async activateView() {
console.log("Activating the TaskSidebarView...");
try {
// Check if there's already a leaf for the sidebar
let leaf = this.app.workspace.getLeavesOfType(VIEW_TYPE_TASK_SIDEBAR).first();
if (!leaf) {
console.log("No existing sidebar leaf found, creating a new one.");
// Create a new right leaf if none exists
const rightLeaf = this.app.workspace.getRightLeaf(false);
if (!rightLeaf) {
console.log("No right leaf found, creating a new one by splitting the workspace.");
leaf = this.app.workspace.createLeafBySplit(this.app.workspace.rootSplit, "horizontal", false);
} else {
leaf = rightLeaf;
}
}
// Set the view state to our custom sidebar view
await leaf.setViewState({
type: VIEW_TYPE_TASK_SIDEBAR,
active: true,
});
// Reveal the sidebar leaf
this.app.workspace.revealLeaf(leaf);
console.log("TaskSidebarView activated and revealed.");
} catch (error) {
console.error("Error while activating the TaskSidebarView:", error);
}
}
showTasksSidebar() {
console.log("Displaying tasks in the sidebar.");
// 今日のファイル名
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
const todayFileName = ${year}-${month}-${day}.md;
// ファイル取得
const todayFile = this.app.vault.getFiles().find(file => file.name === todayFileName);
if (!todayFile) {
console.warn(No file found for today's date: ${todayFileName});
new this.app.Notice(No note found for today's date: ${todayFileName});
return;
}
this.app.vault.read(todayFile).then((noteText) => {
const lines = noteText.split(/\r?\n/);
const endTimes = [];
let currentDateTime = new Date();
for (const rawLine of lines) {
const line = rawLine.trim();
if (!line) continue;
// 正規化(全角ハイフン等の揺れ対策)
const norm = line.normalize('NFKC');
// 対象は「チェックリストで始まる行」のみ(- / - x / - X)
const checklist = norm.match(/^\s*-\s*\[( xX)\]\s*(.*)$/);
if (!checklist) continue;
const checkboxMark = checklist1; // ' ' or 'x'/'X'
const rest = checklist2.trim(); // チェックボックス後の本文
// 完了印(- x/- X)は対象外
if (checkboxMark.toLowerCase() === 'x') continue;
// 完了タスク(hh:mm-hh:mm を含む)は対象外(全/半角ダッシュ対応)
if (/\b\d{2}:\d{2}\s*-–—−\s*\d{2}:\d{2}\b/.test(rest)) continue;
// 本文から開始時刻/所要時間/タスク名を抽出
// 形式: "hh:mm (10) タスク名" ※hh:mm と () はどちらも省略可だが、ここではチェックリスト必須
const m = rest.match(/^(?:(\d{2}:\d{2}))?-?\s*(?:\((\d+)\))?\s*(.*)$/);
if (!m) continue;
const startTime = (m1 || '').trim();
const durationStr = (m2 || '').trim();
const durationMin = durationStr !== '' ? (parseInt(durationStr, 10) || 0) : 0;
let taskName = (m3 || '').trim();
// MarkdownリンクのURL除去: text(url) -> text
taskName = taskName.replace(/\[(^\]+)\]\(^\)+\)/g, "$1");
// 基準時間
let baseTime;
if (startTime) {
const h, mm = startTime.split(':').map(Number);
baseTime = new Date();
baseTime.setHours(h, mm, 0, 0);
} else {
baseTime = new Date(currentDateTime);
}
const actualStartTime = new Date(baseTime);
const startTimeStr = actualStartTime.toTimeString().slice(0, 5);
// 終了時刻(0分でも開始=終了)
const endDate = new Date(baseTime.getTime() + durationMin * 60000);
if (durationMin > 0 && endDate < new Date()) {
endDate.setTime(Date.now());
}
const endTimeStr = endDate.toTimeString().slice(0, 5);
// 残り/超過(開始時刻があり、所要>0のときのみ)
let tailNote = "";
if (startTime && durationMin > 0) {
const elapsedMin = Math.ceil((Date.now() - actualStartTime.getTime()) / 60000);
if (elapsedMin > durationMin) {
tailNote = 🚨超過${elapsedMin - durationMin}分;
} else {
tailNote = ➡️残り${durationMin - elapsedMin}分;
}
}
// 常に (x分) 形式で出力(0分も含む)
endTimes.push(${taskName}||${startTimeStr} - ${endTimeStr} (${durationMin}分)${tailNote});
// 次タスクの基準は終了時刻(0分でも進む)
currentDateTime = endDate;
}
const view = this.app.workspace.getLeavesOfType(VIEW_TYPE_TASK_SIDEBAR)0?.view;
if (view instanceof TaskSidebarView) {
view.updateTasks(endTimes.length ? endTimes : "(本日のタスクは見つかりませんでした)||");
} else {
console.error("Failed to find or initialize the TaskSidebarView.");
}
}).catch((error) => {
console.error("Error reading the file:", error);
});
}
};
code:styles.css
.task-item {
margin-bottom: 1em; /* Add some space between tasks */
}
.task-name {
margin-bottom: -0.5em; /* Reduce space between task name and time */
line-height: 1; /* Slightly reduce line-height */
}
.task-time {
color: #888; /* Gray out the text */
font-size: 0.9em; /* Slightly smaller font size */
line-height: 1; /* Slightly reduce line-height */
}
/* Section (🗂️) specific styling */
.section {
font-weight: bold; /* Make section titles bold */
margin-bottom: 0.5em; /* Add some space after section titles */
line-height: 1.2; /* Adjust line-height for sections */
}